<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div>打开控制台查看内容</div>
    <script>
        console.log('==========属性名表达式==========');
        /* 定义对象的属性有两种方式
           obj.name
           obj['a'+'b'] = 10
           第一种是直接使用标识符作为属性名
           第二种以表达式作为属性名
        */
       let obj = {}
       console.log(obj.name = '孙悟空');  // 孙悟空
       console.log(obj['a' + 'b'] = 10);  // 10
       console.log(obj); // {name: '孙悟空', ab: 10}

       /* es5中字面量定义对象只能使用一种方法 */
       var obj2 = {name:'东方',age:10}

       /* es6允许使用表达式属性名，将表达式放入方括号内即可 */
       let key = 'address'
       let obj3 = {
           name:'不败',
           [key] : '武汉',
           ['a'+'ge'] : 100
       }
       console.log(obj3);  // {name: '不败', address: '武汉', age: 100}
       console.log(obj3.address);  // 武汉
       console.log(obj3[key]);  // 武汉


       /* 表达式还可以定义方法名 */
       let obj4 = {
            ['f'+'n'](){
            console.log('hello');
        },
        fn2 : function(){console.log('word');}
       }
       obj4.fn() // hello
       obj4.fn2()  // word

       /* 解构与属性名表达式不能同时使用 */
       // 报错
       // let fan = 'name'
       // let tion = { [fan] }

       /* 注意： 属性名表达式如果是对象，会被转换成字符串[object object] */
       let o = {a : 1}
       let o2 = {b : 2}

       let obj5 = {
           [o] : '东方不败',
           [o2] : '西方求败'
       }
       console.log(obj5);   // {[object Object]: '西方求败'}
       // 由于属性名表达式都被转换为[object Object]，同键名的会被覆盖，所以这里只输出最后一个{[object Object]: '西方求败'}

       console.log('===========name属性===========');
       /* 函数的name属性返回函数名
          对象方法也是函数，因此也有name属性
       */
      let n = {
        sayHi(){console.log('hello');}
      }
      console.log(n.sayHi.name);   // sayHi
      
      /* 如果对象的方法使用了取值函数：getter或存值函数setter，
         那么的属性在描述对象的get和set身上，需要在方法名前假get和set
      */
     let n2 = {
         get fn(){},
         set fn(x){}
     }
     let desc = Object.getOwnPropertyDescriptor(n2,'fn')
     console.log(desc.get.name);  // get fn
     console.log(desc.set.name);  // set fn
    //  console.log(n2.fn.name);  // Cannot read properties of undefined (reading 'name')

    /* 如果对象的方法是一个Symbol值，name属性返回的是Symbol值的描述 */
    let key1 = Symbol('desc')
    let key2 = Symbol()
    let kobj = {
        [key1](){},
        [key2](){}
    }
    console.log(kobj[key1].name);  // [desc]
    console.log(kobj[key2].name);  // ""
    // key1有描述，返回描述[desc]；key2没有值，返回空

    console.log('===============属性的可枚举性和遍历===========');
    /* 对象的每个属性都存在一个描述对象，用来控制该属性的行为
       获取属性描述对象的方法：
    */
   let d = { a : 123 }
   console.log(Object.getOwnPropertyDescriptor(d,'a'));
   //{value: 123, writable: true, enumerable: true, configurable: true}

   /* 其中，enumerable 为枚举属性，true为可枚举，false为不可枚举 */
   /* 下面四个操作会忽略enumerable为false属性
      1、for...in循环：只遍历对象自身的和继承的可枚举的属性。
      2、Object.keys()：返回对象自身的所有可枚举的属性的键名。
      3、JSON.stringify()：只串行化对象自身的可枚举的属性。
      4、Object.assign()： 忽略enumerable为false的属性，只拷贝对象自身的可枚举的属性。
   */
  /* 其中for...in会返回继承的属性，其它的几个都会忽略继承的属性 */
  /* 如果枚举属性为false，这四个方法遍历时会忽略为false的属性 */

  console.log('===========解构赋值和剩余运算符==========');
   /* 解构：
      可以根据对象的键名直接获取到键值，非常方便
   */
  let { a,b,...z } = { a:1, b:2, c:3, d:4, e:5 }
  console.log(a,b,z);  // 1 2 {c: 3, d: 4, e: 5}

  let info = {
    id:1,
    name:'东方不败'
  }
  let {id ,name} = info
  console.log(id,name);  // 1 '东方不败'

  //   嵌套解构
  // 单层嵌套
  let infos = {
    vals : 1,
    users: {
        names : '东方不败'
    }
  }
  let {vals ,users:{names} } = infos
  console.log(names);  // 东方不败


  // 多层嵌套
  let info2 = {
    val:1,
    user:{
        id2:100,
        name2:'东方不败',
        address:{
            city:'武汉',
            district : '世界城广场'
        }
    }
  }
  let { val,user:{id2,name2,address:{city,district}} } = info2
  console.log(val,id2,name2,city,district);  // 1 100 '东方不败' '武汉' '世界城广场'

  /* 剩余运算符转换字符串 */
  console.log({...'world'});  // {0: 'w', 1: 'o', 2: 'r', 3: 'l', 4: 'd'}

  /* 对象剩余运算符相当于是 Object.assign()*/
  let r = { id : 1, name:'东方', text:'不败'}
  let res = {...r}
  let res2 = Object.assign({},r)
  console.log(res);  // {id: 1, name: '东方', text: '不败'}
  console.log(res2); // {id: 1, name: '东方', text: '不败'}
  console.log(res == res2);  // false

  // 剩余运算符拼接，重复的键名会被覆盖
  let r2 = { a:1 , b:2 , c:3 , d:4}
  let r3 = {c:6,d:7,e:8,f:9}
  console.log({...r2,...r3});  // {a: 1, b: 2, c: 6, d: 7, e: 8,f: 9}

  console.log('===========对象遍历方法===========');
  /* 
  for...in  循环遍历对象自身和继承的可枚举属性（不含Symbol）
  Object.keys 返回一个数组，该数组为对象自身的所有可枚举属性（不含继承属性和Symbol属性）
  Object.getOwnPropertyNames 返回一个数组，包含对象自身的所有（包括不可枚举）属性（不含Symbol属性）
  Object.getOwnPropertySymbols 返回一个数组，只包含对象自身的所有Symbol属性的键名
  Reflect.ownKeys返回一个数组，包含对象自身的（不含继承的）所有键名，（含 Symbol、字符串、不可枚举）。
  */
 let s = Symbol('sym')  // Symbol类型
 let f = {a:1, b:2, c:3, d:4, e:5, [s]:6}

 for (k in f) {
    // 键名
    console.log(k);  // a b c d e
    // 属性
    console.log(f[k]); // 1 2 3 4 5
 }

 // Object.keys返回对象键名数组
 console.log(Object.keys(f));  // ['a', 'b', 'c', 'd', 'e']
 Object.keys(f).forEach(el => console.log(el))  // a b c d e

 // Object.getOwnPropertyNames
 console.log(Object.getOwnPropertyNames(f));  // ['a', 'b', 'c', 'd', 'e']

 // Object.getOwnPropertySymbols
 console.log(Object.getOwnPropertySymbols(f));  // [Symbol(sym)]

 // Reflect.ownKeys
 console.log(Reflect.ownKeys(f));  // ['a', 'b', 'c', 'd', 'e', Symbol(sym)]

//  Symbol是不可枚举的
 let desc2 = Object.getOwnPropertyDescriptor(f,s)
 console.log(desc2); // {value: 6, writable: true, enumerable: true, configurable: true}
 // 在这里返回的是enumerable：true，返回的是对象的枚举属性，并不是Symbol
 // Symbol是不可枚举的，所以keys遍历中没有Symbol

 console.log('=========对象内函数============');
 /* 对象内方法
    对象内可以定义函数，调用方式：对象名.方法名()
    普通函数和箭头函数在对象内都可以定义
 */
 let fn = {
    title:'东方不败',
    sayHi(){console.log('hello')},
    getSum(x){return x % 2 == 0 ? true : false},
    getNum : s = (x) => x + 1
 }
 console.log(fn.title); // 东方不败
 fn.sayHi()  // hello
 console.log(fn.getSum(3)); // false
 console.log(fn.getSum(4)); // true
 console.log(fn.getNum(1)); // 2


 Object.keys(fn).forEach(el=> console.log(el))  // title sayHi getSum getNum
   
console.log('====对象内函数this=====');
/* 对象内函数的this指向问题
   对象内函数:
   普通函数的this指向的是对象本身，也就是当前对象内的所有属性和方法
   箭头函数的this指向为window，因为对象不构成单独的作用域，导致箭头函数定义时的作用域是全局作用域。
*/
window.val = '西方求败'
let fn2 = {
    name : '艺术概论',
    inThis(){ console.log(this.val)},
    inThis2(){ 
        console.log(this.name)
        console.log(this);
     },
     inThis3 : t = () => {
        console.log(this.val)
        console.log(this);
        const t2 = () => {
            console.log(this);
        }
    }
}


function getThis2(){
   console.log(this.val);
 }
 getThis2()  // 西方求败
 fn2.inThis()  // undefined
 fn2.inThis2() // 艺术概论  // {name: '艺术概论', inThis: ƒ, inThis2: ƒ}
 fn2.inThis3() // 西方求败  // Window {window: Window, self: Window, document: document, name: '', location: Location, …}

 /* 
这里分别在对象内定义了普通函数和箭头函数
inThis()输出的是undefined，因为对象内并没有val这个属性，this.val没有找到，返回undefined
inThis2()输出的是艺术概论，this.name就是当前对象内的name，对象内的普通函数this指向的是当前对象
inThis3()为箭头函数，输出西方求败，对象内的箭头函数this指向的是window
 */


    </script>
</body>
</html>